package com.leinao.media.utils;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.leinao.media.callback.DhDisConnectCallBack;
import com.leinao.media.entity.Camera;
import com.leinao.media.entity.VideoInfo;
import com.leinao.media.enums.DhErrorEnum;
import com.leinao.media.exception.MediaException;
import com.leinao.media.sdk.dh.common.DisConnect;
import com.leinao.media.sdk.dh.lib.NetSDKLib;
import com.leinao.media.sdk.dh.lib.ToolKits;
import com.leinao.media.sdk.dh.lib.Utils;
import com.leinao.media.sdk.dh.lib.structure.NET_RECORD_STATUS;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * 大华设备操作工具类
 *
 * @author zhangyu
 * @date 2022/2/20 20:00
 */
public class DhUtil {

    private static final Log log = LogFactory.get();

    /**
     * 实例化SDK
     */
    public static NetSDKLib DH_NET_SDK = NetSDKLib.NETSDK_INSTANCE;

    /**
     * DH设备断线通知回调
     */
    private static DisConnect disConnect = new DisConnect();

//    /**
//     * 设备信息
//     */
//    public static NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();

    /**
     * 设备信息map
     */
    static Map<String, NetSDKLib.NET_DEVICEINFO_Ex> DEVICE_INFO_MAP = new HashMap<>();

    /**
     * 文件下载返回句柄缓存
     */
    private static Map<String, Long> handleCache = new HashMap<>();

    /**
     * 断线重连回调函数
     */
    public static DhDisConnectCallBack dhDisConnectCallBack = new DhDisConnectCallBack();

    /**
     * sdk 初始化
     * 对整个网络 SDK 系统的初始化，内存预分配等操作
     */
    public static void init() {
        if (!DH_NET_SDK.CLIENT_Init(disConnect, null)) {
            log.error("DH_SDK初始化失败,错误码：{}，错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        log.info("大华SDK完成初始化...");
        // 打开日志，可选
        NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO();
        File path = new File(".");
        String logPath = path.getAbsoluteFile().getParent() + File.separator + "sdklog" + File.separator + "DH" + File.separator +
                ToolKits.getDate() + ".log ";
        setLog.bSetFilePath = 1;
        System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length);
        setLog.bSetPrintStrategy = 1;
        setLog.nPrintStrategy = 0;
        boolean bool = DH_NET_SDK.CLIENT_LogOpen(setLog);
        if (!bool) {
            log.error("大华SDK日志开启失败,错误码:{},错误原因：{}" + ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        // 设置断线重连回调接口，设置过断线重连成功回调函数后，当设备出现断线情况，SDK内部会自动进行重连操作
        // 此操作为可选操作，但建议用户进行设置
        DH_NET_SDK.CLIENT_SetAutoReconnect(dhDisConnectCallBack, null);

        // 设置登录超时时间和尝试次数，可选
        // 登录请求响应超时时间设置为5S
        int waitTime = 5000;
        // 登录时尝试建立链接3次
        int tryTimes = 3;
        DH_NET_SDK.CLIENT_SetConnectTime(waitTime, tryTimes);

        // 设置更多网络参数，NET_PARAM的nWaittime，nConnectTryNum成员与CLIENT_SetConnectTime
        // 接口设置的登录设备超时时间和尝试次数意义相同,可选
        NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM();
        // 登录时尝试建立链接的超时时间
        netParam.nConnectTime = 10000;
        // 设置子连接的超时时间
        netParam.nGetConnInfoTime = 3000;
        // 获取设备信息超时时间，为0默认1000ms
        netParam.nGetDevInfoTime = 3000;
        DH_NET_SDK.CLIENT_SetNetworkParam(netParam);
    }

    /**
     * 登录
     *
     * @param ip       IP
     * @param port     端口
     * @param userName 用户名
     * @param password 密码
     * @return 用户id
     */
    public static long login(String ip, String port, String userName, String password) {
        // 入参
        NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam = new NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY();
        pstInParam.nPort = Integer.parseInt(port);
        pstInParam.szIP = ip.getBytes();
        pstInParam.szPassword = password.getBytes();
        pstInParam.szUserName = userName.getBytes();
        // 出参
        NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY();
        NetSDKLib.NET_DEVICEINFO_Ex deviceinfo = new NetSDKLib.NET_DEVICEINFO_Ex();
        pstOutParam.stuDeviceInfo = deviceinfo;
        // 缓存设备信息
        DEVICE_INFO_MAP.put(ip, deviceinfo);

        NetSDKLib.LLong loginHandle = DH_NET_SDK.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam);
        long uid = loginHandle.longValue();
        if (uid == 0) {
            log.error("{},设备注册失败,错误码：{},错误原因：{}", ip, ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("大华设备注册失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        } else {
            log.info("{},设备注册成功", ip);
        }
        return uid;
    }

    /**
     * 用户登出
     *
     * @param uid 登录句柄
     * @return boolean
     */
    public static boolean logout(long uid) {
        log.info("DH销毁当前登录信息,uid:{}", uid);
        boolean bool = DH_NET_SDK.CLIENT_Logout(new NetSDKLib.LLong(uid));
        log.info("DH销毁当前登录信息成功与否：{}", bool);
        if (!bool) {
            log.error("DH用户：{},登出失败,错误码：{},错误原因：{}", uid, ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("DH用户登出失败，错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return bool;
    }

    /**
     * 释放SDK资源
     */
    public static void cleanup() {
        DH_NET_SDK.CLIENT_Cleanup();
        log.info("大华SDK释放资源...");
        System.exit(0);
    }

    /**
     * 获取设备状态
     *
     * @param uid 登录句柄
     * @return String
     */
    public static String getDeviceStatus(long uid) {
        IntByReference retLen = new IntByReference(0);
        Pointer p = new Memory(Integer.SIZE);
        p.clear(Integer.SIZE);
        if (!DH_NET_SDK.CLIENT_QueryDevState(new NetSDKLib.LLong(uid), NetSDKLib.NET_DEVSTATE_ONLINE, p, Integer.SIZE, retLen, 3000)) {
            log.error("查询设备在线状态失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        int[] a = new int[1];
        p.read(0, a, 0, 1);
        int status = a[0];
        // 1表示在线,0表示离线
        if (status == 1) {
            log.info("设备在线");
            return "在线";
        } else if (status == 0) {
            log.info("设备离线");
            return "离线";
        } else {
            return "";
        }
    }

    /**
     * 获取NVR下面的摄像机列表
     *
     * @param uid 登录句柄
     * @return 相机列表
     */
    public static List<Camera> getCameraList(String ip, long uid, String userName, String password) {
        // 初始化结果集
        List<Camera> cameraList = new ArrayList<>();

        // 显示源信息数组初始化
        int nCameraCount = 60;
        NetSDKLib.NET_MATRIX_CAMERA_INFO[] cameras = new NetSDKLib.NET_MATRIX_CAMERA_INFO[nCameraCount];
        for (int i = 0; i < nCameraCount; i++) {
            cameras[i] = new NetSDKLib.NET_MATRIX_CAMERA_INFO();
        }
        // 入参
        NetSDKLib.NET_IN_MATRIX_GET_CAMERAS stuIn = new NetSDKLib.NET_IN_MATRIX_GET_CAMERAS();
        // 出参
        NetSDKLib.NET_OUT_MATRIX_GET_CAMERAS stuOut = new NetSDKLib.NET_OUT_MATRIX_GET_CAMERAS();

        stuOut.nMaxCameraCount = nCameraCount;
        stuOut.pstuCameras = new Memory((long) cameras[0].size() * nCameraCount);
        stuOut.pstuCameras.clear((long) cameras[0].size() * nCameraCount);
        // 将数组内存拷贝到Pointer
        ToolKits.SetStructArrToPointerData(cameras, stuOut.pstuCameras);
        if (DH_NET_SDK.CLIENT_MatrixGetCameras(new NetSDKLib.LLong(uid), stuIn, stuOut, 5000)) {
            // 将 Pointer 的内容 输出到 数组
            ToolKits.GetPointerDataToStructArr(stuOut.pstuCameras, cameras);
            for (int j = 0; j < stuOut.nRetCameraCount; j++) {
                if (cameras[j].bRemoteDevice != 0) {
                    // IP
                    String cameraIp = new String(cameras[j].stuRemoteDevice.szIp).trim();
                    // 剔除无效通道
                    if (StrUtil.equals(cameraIp, "192.168.0.0")) {
                        continue;
                    }
                    Camera camera = new Camera();
                    camera.setId(cameras[j].nUniqueChannel);
                    camera.setIp(cameraIp);
                    camera.setPort(cameras[j].stuRemoteDevice.nPort);
                    camera.setChannel(NullUtil.valueOf(cameras[j].nUniqueChannel));
                    try {
                        // 查询通道名称
                        NetSDKLib.AV_CFG_ChannelName msg = new NetSDKLib.AV_CFG_ChannelName();
                        ToolKits.GetDevConfig(new NetSDKLib.LLong(uid), cameras[j].nUniqueChannel, NetSDKLib.CFG_CMD_CHANNELTITLE, msg);
                        camera.setName(new String(msg.szName, Utils.getPlatformEncode()).trim());
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    // 相机状态查询
                    Map<Integer, Integer> map = queryChannelState(ip, uid);
                    Integer status = map.get(Integer.parseInt(camera.getChannel()));
                    camera.setStatus(NullUtil.valueOf(status - 1));
                    // 视频url
                    StringBuilder sb = new StringBuilder();
                    // 为了避免受服务器CPU以及带宽的影响导致视频播放问题,这里统一采用子码流地址播放(subtype=1)
                    sb.append("rtsp://").append(userName).append(":").append(password).append("@").append(ip)
                            .append(":554/cam/realmonitor?channel=").append(Integer.parseInt(camera.getChannel()) + 1).append("&subtype=1");
                    camera.setUrl(sb.toString());
                    cameraList.add(camera);
                }
            }
        } else {
            log.error("获取所有有效相机列表失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return cameraList;
    }

    /**
     * 同步抓图
     *
     * @param uid         登录句柄
     * @param channel     通道号
     * @param saveAddress 文件路径
     * @return 图片相对路径
     */
    public static String captureJpeg(long uid, int channel, String saveAddress, Integer quality) {
        // 图片名
        String picName = saveAddress + System.currentTimeMillis() + ".jpg";
        // 入参
        NetSDKLib.NET_IN_SNAP_PIC_TO_FILE_PARAM inParam = new NetSDKLib.NET_IN_SNAP_PIC_TO_FILE_PARAM();
        // 出参
        NetSDKLib.NET_OUT_SNAP_PIC_TO_FILE_PARAM outParam = new NetSDKLib.NET_OUT_SNAP_PIC_TO_FILE_PARAM(8);

        inParam.stuParam.Channel = channel;
        // 画质不传，默认是3-中等
        if (null == quality) {
            inParam.stuParam.Quality = 3;
        } else {
            inParam.stuParam.Quality = quality.shortValue();
        }
        inParam.stuParam.ImageSize = 1;
        inParam.stuParam.mode = 0;
        inParam.stuParam.InterSnap = 5;
        inParam.stuParam.CmdSerial = 1;

        // 创建文件夹
        FileUtil.mkdir(saveAddress);
        new File(picName);

        System.arraycopy(picName.getBytes(), 0, inParam.szFilePath, 0, picName.getBytes().length);
        // 超时时间 5s
        int timeOut = 5000;
        // 同步抓图
        boolean bool = DH_NET_SDK.CLIENT_SnapPictureToFile(new NetSDKLib.LLong(uid), inParam, outParam, timeOut);
        log.info("同步抓图结果：{}", bool);

        if (!bool) {
            log.error("截图失败,请确认相机状态,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("截图失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return picName;
    }

    /**
     * 云台控制
     *
     * @param uid      登录句柄
     * @param iChannel 通道号
     * @param command  云台命令
     * @param dwStop   是否停止 0-开启 1-停止
     * @param speed    速度，范围1-8,8效果最明显
     * @return String
     */
    public static String cloudPlatformControl(long uid, Integer iChannel, Integer command, Integer dwStop, Integer speed) {
        boolean bool = DH_NET_SDK.CLIENT_DHPTZControlEx(new NetSDKLib.LLong(uid), iChannel, command,
                speed, speed, 0, dwStop);
        if (!bool) {
            log.error("云台控制失败，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("云台控制失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return "";
    }

    /**
     * 预置位
     *
     * @param uid            登陆句柄
     * @param iChannel       通道号
     * @param dwPTZPresetCmd 预置位命令
     * @param dwPresetIndex  预置位下标
     * @return boolean
     */
    public static boolean setPresetLocation(long uid, Integer iChannel, Integer dwPTZPresetCmd, Integer dwPresetIndex) {
        boolean bool = DH_NET_SDK.CLIENT_DHPTZControlEx(new NetSDKLib.LLong(uid), iChannel, dwPTZPresetCmd, 0, dwPresetIndex, 0, 0);
        if (!bool) {
            log.error("预置位失败，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("预置位失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return bool;
    }

    /**
     * 录像文件查找
     *
     * @param uid       登陆句柄
     * @param channel   通道号
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return JSONObject
     */
    public static JSONObject queryRecordFile(long uid, int channel, Date startTime, Date endTime) {

        JSONObject jsonObject = new JSONObject();

        // RecordFileType 录像类型 0:所有录像  1:外部报警  2:动态监测报警  3:所有报警  4:卡号查询   5:组合条件查询
        int nRecordFileType = 0;

        // 开始时间
        NetSDKLib.NET_TIME tmStart = new NetSDKLib.NET_TIME();
        tmStart.dwYear = DateUtil.year(startTime);
        tmStart.dwMonth = DateUtil.month(startTime) + 1;
        tmStart.dwDay = DateUtil.dayOfMonth(startTime);
        tmStart.dwHour = DateUtil.hour(startTime, true);
        tmStart.dwMinute = DateUtil.minute(startTime);
        tmStart.dwSecond = DateUtil.second(startTime);

        // 结束时间
        NetSDKLib.NET_TIME tmEnd = new NetSDKLib.NET_TIME();
        tmEnd.dwYear = DateUtil.year(endTime);
        tmEnd.dwMonth = DateUtil.month(endTime) + 1;
        tmEnd.dwDay = DateUtil.dayOfMonth(endTime);
        tmEnd.dwHour = DateUtil.hour(endTime, true);
        tmEnd.dwMinute = DateUtil.minute(endTime);
        tmEnd.dwSecond = DateUtil.second(endTime);

        // 录像文件信息
        NetSDKLib.NET_RECORDFILE_INFO[] stFileInfo = (NetSDKLib.NET_RECORDFILE_INFO[]) new NetSDKLib.NET_RECORDFILE_INFO().toArray(2000);
        IntByReference nFindCount = new IntByReference(0);

        boolean bool = DH_NET_SDK.CLIENT_QueryRecordFile(new NetSDKLib.LLong(uid), channel,
                nRecordFileType, tmStart, tmEnd, null, stFileInfo,
                stFileInfo.length * stFileInfo[0].size(), nFindCount, 5000, false);

        if (!bool) {
            log.error("录像文件查找失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("录像文件查找失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }

        // 视频详情
        List<VideoInfo> videoInfoList = new ArrayList<>();
        // 总录像时长
        long sumVideoTime = 0;
        for (NetSDKLib.NET_RECORDFILE_INFO info : stFileInfo) {
            // 文件大小
            int size = info.size;
            if (0 == size) {
                continue;
            }
            // 文件名
            String fileName = "";
            try {
                fileName = new String(info.filename, Utils.getPlatformEncode()).trim();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            VideoInfo videoInfo = new VideoInfo();
//            videoInfo.setVideoName(fileName.substring(fileName.lastIndexOf("/") + 1));
            videoInfo.setVideoName(generateRandom(12) + "");
            videoInfo.setStartTime(netTimeToDate(info.starttime));
            videoInfo.setEndTime(netTimeToDate(info.endtime));
            // 文件大小
            int iTemp;
            String videoSize;
            if (info.size < 1024) {
                iTemp = info.size;
                videoSize = iTemp + "K";
            } else {
                iTemp = (info.size) / 1024;
                videoSize = iTemp + "M   ";
                iTemp = (info.size) % 1024;
                videoSize = videoSize + iTemp + "K";
            }
            videoInfo.setVideoSize(videoSize);
            // 录像时长
            long videoTime = DateUtil.between(videoInfo.getEndTime(), videoInfo.getStartTime(), DateUnit.MINUTE);
            videoInfo.setVideoTime(videoTime);
            // 录像地址
            videoInfo.setVideoUrl("/media/data/images/video/" + videoInfo.getVideoName() + ".avi");
            videoInfoList.add(videoInfo);

            sumVideoTime = sumVideoTime + videoTime;
        }
        jsonObject.put("videoInfoList", videoInfoList);
        jsonObject.put("sumVideoTime", sumVideoTime);
        return jsonObject;
    }

    /**
     * 获取摄像机通道状态
     *
     * @param uid 登录句柄
     * @return HashMap<Integer, Integer> key：通道号 value：通道状态
     */
    private static Map<Integer, Integer> queryChannelState(String ip, long uid) {
        Map<Integer, Integer> map = new HashMap<>(16);
        int nQueryType = NetSDKLib.NET_QUERY_GET_CAMERA_STATE;
        // 入参
        NetSDKLib.NET_IN_GET_CAMERA_STATEINFO stIn = new NetSDKLib.NET_IN_GET_CAMERA_STATEINFO();
        // 1-true,查询所有摄像机状态
        stIn.bGetAllFlag = 1;
        // 摄像机通道信息
        int chnCount = DEVICE_INFO_MAP.get(ip).byChanNum;
        NetSDKLib.NET_CAMERA_STATE_INFO[] cameraInfo = new NetSDKLib.NET_CAMERA_STATE_INFO[chnCount];
        for (int i = 0; i < chnCount; i++) {
            cameraInfo[i] = new NetSDKLib.NET_CAMERA_STATE_INFO();
        }
        // 出参
        NetSDKLib.NET_OUT_GET_CAMERA_STATEINFO stOut = new NetSDKLib.NET_OUT_GET_CAMERA_STATEINFO();
        stOut.nMaxNum = chnCount;
        stOut.pCameraStateInfo = new Memory((long) cameraInfo[0].size() * chnCount);
        stOut.pCameraStateInfo.clear((long) cameraInfo[0].size() * chnCount);
        // 将数组内存拷贝到Pointer
        ToolKits.SetStructArrToPointerData(cameraInfo, stOut.pCameraStateInfo);
        stIn.write();
        stOut.write();
        boolean bRet = DH_NET_SDK.CLIENT_QueryDevInfo(new NetSDKLib.LLong(uid), nQueryType, stIn.getPointer(), stOut.getPointer(),
                null, 3000);
        stIn.read();
        stOut.read();
        if (bRet) {
            // 将Pointer的内容输出到数组
            ToolKits.GetPointerDataToStructArr(stOut.pCameraStateInfo, cameraInfo);
//            log.info("查询到的摄像机通道状态有效个数：" + stOut.nValidNum);
            for (int i = 0; i < stOut.nValidNum; i++) {
//                log.info("通道号：" + cameraInfo[i].nChannel);
//                log.info("连接状态：" + cameraInfo[i].emConnectionState);
                map.put(cameraInfo[i].nChannel, cameraInfo[i].emConnectionState);
            }
        } else {
            log.error("获取摄像机通道状态失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return map;
    }

    /**
     * 录像文件下载(按时间)
     *
     * @param uid         登录句柄
     * @param channel     通道号
     * @param fileName    文件名
     * @param startTime   开始时间
     * @param endTime     结束时间
     * @param saveAddress 保存路径
     * @return boolean
     */
    public static long downloadVideoFileByTime(long uid, int channel, String fileName, Date startTime, Date endTime,
                                               String saveAddress) {
        // 设置回放时的码流类型，1为主码流
        setStreamType(uid);

        // 码流转换类型,0-DAV码流(默认); 1-PS流,3-MP4
        int scType = 3;
        // 下载所有录像
        int nRecordFileType = NetSDKLib.EM_QUERY_RECORD_TYPE.EM_RECORD_TYPE_ALL;
        NetSDKLib.LLong downLoadHandle = DH_NET_SDK.CLIENT_DownloadByTimeEx2(new NetSDKLib.LLong(uid), channel, nRecordFileType,
                dateToNetTime(startTime), dateToNetTime(endTime), saveAddress,
                null, null, null, null, scType, null);
        if (downLoadHandle.longValue() != 0) {
            // 缓存句柄
            handleCache.put("iDownHandle-" + fileName, downLoadHandle.longValue());
            return downLoadHandle.longValue();
        } else {
            log.error("录像下载失败，错误码：{},错误原因：{}", uid, ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            return 0L;
        }
    }

    /**
     * 获取下载进度
     *
     * @param iDownHandle 开始下载接口的返回值
     * @return 下载进度
     */
    public static int getDownLoadProgress(long iDownHandle) {
        // 文件总大小
        IntByReference totalSize = new IntByReference(0);
        // 已下载文件大小
        IntByReference downLoadSize = new IntByReference(0);

        boolean bool = DH_NET_SDK.CLIENT_GetDownloadPos(new NetSDKLib.LLong(iDownHandle), totalSize, downLoadSize);
        if (!bool) {
            log.error("录像文件下载进度查询失败,错误码：{}", ToolKits.getErrorCodePrint());
            throw new MediaException("录像文件下载进度查询失败,错误原因:" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }

        int totalSizeValue = totalSize.getValue();
        int downLoadSizeValue = downLoadSize.getValue();

        return downLoadSizeValue * 100 / totalSizeValue;
    }

    /**
     * 停止下载录像
     *
     * @param hDownLoadHandle 下载句柄
     */
    public static void stopDownLoadRecordFile(NetSDKLib.LLong hDownLoadHandle) {
        log.info("句柄：{},停止下载", hDownLoadHandle.longValue());
        if (hDownLoadHandle.longValue() == 0) {
            return;
        }
        boolean bool = DH_NET_SDK.CLIENT_StopDownload(hDownLoadHandle);
        if (!bool) {
            log.info("句柄：{},停止下载出现错误，错误码：{},错误原因：{}", hDownLoadHandle.longValue(), ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("停止下载出现错误,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        log.info("句柄：{},停止下载成功", hDownLoadHandle.longValue());
    }

    /**
     * 停止全部下载录像
     */
    public static void stopAllDownLoadRecordFile() {
        log.info("handleCache:{}", JSON.toJSONString(handleCache));
        Iterator<String> iterator = handleCache.keySet().iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            Long iDownHandle = handleCache.get(next);
            log.info("调用停止下载接口,下载句柄:{}", iDownHandle);
            boolean bool = DH_NET_SDK.CLIENT_StopDownload(new NetSDKLib.LLong(iDownHandle));
            if (!bool) {
                log.error("停止下载接口调用失败，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                        DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
                throw new MediaException("停止下载接口调用失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            }
            // 删除下载句柄缓存数据
            iterator.remove();
            handleCache.remove(next);
        }
    }

    /**
     * 录像月历查询
     *
     * @param uid     用户ID
     * @param channel 通道号
     * @param year    年
     * @param month   月
     */
    public static JSONArray getVideoMonth(long uid, int channel, int year, int month) {

        JSONArray jsonArray = new JSONArray();

        NetSDKLib.NET_TIME monthTime = new NetSDKLib.NET_TIME();
        monthTime.dwYear = year;
        monthTime.dwMonth = month;

        NET_RECORD_STATUS status = new NET_RECORD_STATUS();
        monthTime.write();
        status.write();
        boolean bool = DH_NET_SDK.CLIENT_QueryRecordStatus(new NetSDKLib.LLong(uid), channel, NetSDKLib.EM_QUERY_RECORD_TYPE.EM_RECORD_TYPE_ALL,
                monthTime.getPointer(), null, status.getPointer(), 3000);
        if (!bool) {
            log.error("查询月录像状态失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            return jsonArray;
        }
        status.read();
        for (int i = 0; i < status.flag.length; i++) {
            int day = i + 1;
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(String.valueOf(day), status.flag[i]);
            jsonArray.add(jsonObject);
        }
        return jsonArray;
    }

    /**
     * 录像回放码流地址获取
     *
     * @param ip        DVR的IP
     * @param userName  用户名
     * @param passWord  密码
     * @param channel   通道号
     * @param startTime 录像开始时间
     * @param endTime   录像结束时间
     * @return 录像回放码流地址
     */
    public static String getPlayBackAddress(String ip, String userName, String passWord, String channel,
                                            Date startTime, Date endTime) {
        StringBuilder address = new StringBuilder();
        if (null == startTime) {
            log.error("请输入录像回放开始时间");
            return "";
        }
        address.append("rtsp://")
                .append(userName)
                .append(":")
                .append(passWord)
                .append("@")
                .append(ip)
                .append(":554")
                .append("/cam/playback?channel=")
                .append((Integer.parseInt(channel) + 1))
                .append("&subtype=0")
                .append("&starttime=")
                .append(DateUtil.format(startTime, "yyyy_MM_dd_HH_mm_ss"));
        if (null != endTime) {
            address.append("&endtime=")
                    .append(DateUtil.format(endTime, "yyyy_MM_dd_HH_mm_ss"));

        }
        return address.toString();
    }

    /**
     * 获取设备硬盘信息
     *
     * @param uid 登录句柄
     * @return JSONObject
     */
    public static JSONObject getDeviceDiskInfo(long uid) {
        IntByReference intRetLen = new IntByReference();
        NetSDKLib.NET_DEV_HARDDISK_STATE diskInfo = new NetSDKLib.NET_DEV_HARDDISK_STATE();
        if (DH_NET_SDK.CLIENT_QueryDevState(new NetSDKLib.LLong(uid), NetSDKLib.NET_DEVSTATE_DISK, diskInfo.getPointer(),
                diskInfo.size(), intRetLen, 5000)) {
            diskInfo.read();
            // 总容量
            int sum = 0;
            // 剩余容量
            int free = 0;
            for (int i = 0; i < diskInfo.dwDiskNum; ++i) {
                sum = sum + diskInfo.stDisks[i].dwVolume;
                free = free + diskInfo.stDisks[i].dwFreeSpace;
            }
            // 已使用
            int used = sum - free;

            JSONObject jsonObject = new JSONObject();
            jsonObject.put("sum", NumberUtil.div(sum, 1024, 2));
            jsonObject.put("free", NumberUtil.div(free, 1024, 2));
            jsonObject.put("used", NumberUtil.div(used, 1024, 2));
            return jsonObject;
        } else {
            log.error("获取设备硬盘信息出现错误，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return null;
    }

    /**
     * 获取录像总时长
     *
     * @param uid 用户句柄
     * @return 录像总时长
     */
    public static long getSumVideoTime(String ip, long uid, String userName, String password) {
        // 获取录像总时长
        long sumTime = 0;
        // step：
        // 1.获取设备下面所有通道摄像机
        List<Camera> cameraList = getCameraList(ip, uid, userName, password);
        // 2.遍历每个摄像机，得到每个摄像机的录像起止时间
        for (Camera camera : cameraList) {
            String channel = camera.getChannel();
            // 暂定取2000年1月1日
            String startDate = "2000-01-01 00:00:00";
            // 当前时间
            String endDate = DateUtil.now();
            if (StrUtil.isNotBlank(startDate) && StrUtil.isNotBlank(endDate)) {
                // 3.根据每个摄像机的录像起止时间，算出每个摄像机的录像总时长
                JSONObject videoFile = queryRecordFile(uid, Integer.parseInt(channel), DateUtil.parse(startDate, "yyyy-MM-dd HH:mm:ss"),
                        DateUtil.parse(endDate, "yyyy-MM-dd HH:mm:ss"));
                long time = (long) videoFile.get("sumVideoTime");
                sumTime = sumTime + time;
            }
        }
        return sumTime;
    }

    /**
     * 开始录像
     *
     * @param uid     用户ID
     * @param channel 通道号
     * @return 视频预览返回值
     */
    public static long startMakeVideo(long uid, int channel, String saveAddress) {
        // 实时预览
        int playType = NetSDKLib.NET_RealPlayType.NET_RType_Realplay;
        NetSDKLib.LLong lRealHandle = DH_NET_SDK.CLIENT_RealPlayEx(new NetSDKLib.LLong(uid), channel, null, playType);
        if (lRealHandle.longValue() != 0) {
            log.info("视频录制已开启...");
        } else {
            log.error("视频录制错误,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            return -1;
        }
        boolean bool = DH_NET_SDK.CLIENT_SaveRealData(lRealHandle, saveAddress);
        if (!bool) {
            log.error("保存录像失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("保存录像失败,错误原因：" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return lRealHandle.longValue();
    }

    /**
     * 停止录像
     *
     * @param lRealHandle 实时预览返回值
     */
    public static void stopMakeVideo(long lRealHandle) {
        if (-1 == lRealHandle) {
            return;
        }
        if (!DH_NET_SDK.CLIENT_StopSaveRealData(new NetSDKLib.LLong(lRealHandle))) {
            log.error("停止录像失败，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        } else {
            log.info("停止录像成功");
        }
        if (!DH_NET_SDK.CLIENT_StopRealPlay(new NetSDKLib.LLong(lRealHandle))) {
            log.error("停止实时预览失败，错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        } else {
            log.info("停止实时预览成功");
        }
    }

    /**
     * NET_TIME转Date
     *
     * @param netTime NET_TIME
     * @return Date
     */
    private static Date netTimeToDate(NetSDKLib.NET_TIME netTime) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(netTime.dwYear, netTime.dwMonth - 1, netTime.dwDay, netTime.dwHour, netTime.dwMinute, netTime.dwSecond);
        return calendar.getTime();
    }

    /**
     * Date转NET_TIME
     *
     * @param date Date
     * @return NET_TIME
     */
    private static NetSDKLib.NET_TIME dateToNetTime(Date date) {
        NetSDKLib.NET_TIME netTime = new NetSDKLib.NET_TIME();
        netTime.dwYear = DateUtil.year(date);
        netTime.dwMonth = DateUtil.month(date) + 1;
        netTime.dwDay = DateUtil.dayOfMonth(date);
        netTime.dwHour = DateUtil.hour(date, true);
        netTime.dwMinute = DateUtil.minute(date);
        netTime.dwSecond = DateUtil.second(date);
        return netTime;
    }

    /**
     * 设置回放时的码流类型
     */
    private static boolean setStreamType(long uid) {
        // 回放录像枚举1
        int emType = NetSDKLib.EM_USEDEV_MODE.NET_RECORD_STREAM_TYPE;
        // 0-主辅码流,1-主码流,2-辅码流
        IntByReference steamType = new IntByReference(1);
        boolean bool = DH_NET_SDK.CLIENT_SetDeviceMode(new NetSDKLib.LLong(uid), emType, steamType.getPointer());
        if (!bool) {
            log.error("设置回放时的码流类型失败,错误码：{},错误原因：{}", ToolKits.getErrorCodePrint(),
                    DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
            throw new MediaException("设置回放时的码流类型失败,错误原因:" + DhErrorEnum.getErrorMsgByCode(ToolKits.getErrorCodePrint()));
        }
        return bool;
    }

    /**
     * 生成指定长度的随机数
     *
     * @param length 长度
     * @return 随机数
     */
    private static long generateRandom(int length) {
        Random random = new Random();
        char[] digits = new char[length];
        digits[0] = (char) (random.nextInt(9) + '1');
        for (int i = 1; i < length; i++) {
            digits[i] = (char) (random.nextInt(10) + '0');
        }
        return Long.parseLong(new String(digits));
    }

}
